package model;

import model.interfaces.selectable.InstanceTypeAndHealth;
import model.interfaces.selectable.SelectableCommand;
import model.interfaces.selectable.SelectableInstance;
import model.interfaces.selectable.SelectableStructure;
import java.util.EnumMap;
import java.util.LinkedList;
import java.util.Map;
import _bye_util.IntRecycler;

/*
 * 
 * 
 A structure is an immobile thing which has a specific location, stats, and purpose. 
 This iteration shall define the following entities:

 Base - manufactures and heals damaged units

 Offensive damage - damage dealt when attacking
 Defensive damage - damage dealt when fending off an attack
 Armor - absorbs a fixed amount of damage
 Production rates - the number of ticks required to produce a unit of a specific type. 
 (Note: time to heal a damaged unit is computed as % damage * production rate.)
 Health - when reaches zero, the base is destroyed
 Upkeep - resources required to keep structure at full health; 
 lack of required resources will adversely affect the health of a structure
 When all of a player's structure have been destroyed the game is over.
 * 
 * 
 */
/**
 * Represents common attributes of a structure. More specific attributes
 * implemented in subclasses Base and Tower which can be instantiated.
 * 
 * @author - Chris
 */
abstract class Structure implements SelectableInstance, SelectableStructure, Individuals,
        ModelInstance
{
    public Structure( IntRecycler i, InstanceType type, String name, int bricks, int max_bricks,
                      int health, int max_health, int armor, int attack_power, int defense_power,
                      int upkeepRed, int upkeepGreen, int upkeepBlue ) {
        // Type specific stats
        this.type = type;
        this.id = i.newInt();
        this.zeIntRecycler = i;
        this.name = name;
        this.bricks = bricks;
        this.max_bricks = max_bricks;
        this.health = health;
        this.max_health = max_health;
        this.armor = armor;
        this.attack_power = attack_power;
        this.defense_power = defense_power;
        this.upkeep = new EnumMap< ResourceType, Integer >( ResourceType.class );
        this.upkeep.put( ResourceType.RED, upkeepRed );
        this.upkeep.put( ResourceType.GREEN, upkeepGreen );
        this.upkeep.put( ResourceType.BLUE, upkeepBlue );
        
        // type inspecific attributes
        directionFacing = Direction.N;
        isAttacking = false;
        isDefending = false;
        state = new State();
        commands = new LinkedList< Command >();
        commands.add( new Wait() );
        
        // initialize available commands
        availCommands = new LinkedList< SelectableCommand >();
        availCommands.add( new Attack() );
        availCommands.add( new Defend() );
        availCommands.add( new Decommission() );
        availCommands.add( new PowerUp() );
        availCommands.add( new PowerDown() );
    }
    
    // attributes
    private InstanceType type;
    private String name;
    private int id;
    private int bricks; // a measure of how much the construction is complete.
    private int max_bricks;
    private int health;
    private int max_health;
    private int armor;
    private int attack_power;
    private int defense_power;
    private Map< ResourceType, Integer > upkeep;
    private IntRecycler zeIntRecycler;
    
    // combat status variable
    private Direction directionFacing;
    private boolean isAttacking;
    private boolean isDefending;
    
    // beginning values not specific to Structure type.
    private State state;
    private LinkedList< Command > commands;
    private LinkedList< SelectableCommand > availCommands;
    
    /**
     * Adds a new command to the list of available commands.
     * 
     * @param newComm
     *            the new command the structure can respond to.
     */
    protected void addAvailCommand( SelectableCommand newComm )
    {
        availCommands.add( newComm );
    }
    
    // public methods
    /**
     * builds the structure by a specified number. takes max_bricks to complete
     * building the structure. Default max_bricks is 100.
     * 
     * @param count
     *            is number of bricks ranged 0 to maxBricks
     */
    public void addBricks( int count )
    {
        bricks += count;
        if ( bricks > max_bricks ) bricks = max_bricks;
    }
    
    public void defend( Direction dir )
    {
        directionFacing = dir;
        isDefending = true;
        isAttacking = false;
    }
    
    /**
     * 
     * @return if the building is built or not.
     */
    public boolean isCompleted()
    {
        return bricks >= max_bricks;
    }
    
    /**
     * 
     * @return if the building is destroyed or not.
     */
    public boolean isDestroyed()
    {
        return health <= 0;
    }
    
    /**
     * structure takes damage first to armor then to hp. if structure is
     * defending in direction of attack, returns full defense_power
     * (defense_power is damage dealt when fending off an attack.) else, it
     * returns 0. Default max_health is 100. The structure automatically
     * retaliates.
     * 
     * @param damage
     *            is 0 to max_health the structure takes.
     * @param direction
     *            is the direction to take damage from.
     * @return the damage done by a retaliation.
     */
    public int takeDamage( int damage, Direction direction )
    {
        
        takeDamage( damage );
        
        if ( directionFacing == direction.getOpposite() && isDefending )
            return defense_power;
        else
            return 0;
    }
    
    /**
     * structure takes damage first to armor then to hp. if structure is
     * defending in direction of attack, returns full defense_power
     * (defense_power is damage dealt when fending off an attack.) else, it
     * returns 0. Default max_health is 100.
     * 
     * @param damage
     *            the damage the structure takes from an attack.
     */
    public void takeDamage( int damage )
    {
        int armordamage = Math.min( this.armor, damage );
        this.armor = this.armor - armordamage;
        damage = damage - armordamage;
        
        this.health = Math.max( 0, this.health - damage );
    }
    
    /**
     * Deviation: not needed. Health reporting handled by getStatus.
     * 
     * @deprecated
     * 
     * @return how much it is damaged.
     */
    public int getDamage()
    {
        throw new UnsupportedOperationException();
    }
    
    /**
     * @return the direction the structure is attacking.
     */
    public Direction getDirection()
    {
        return directionFacing;
    }
    
    /**
     * @return if it is attacking or not.
     */
    public boolean isAttacking()
    {
        return isAttacking;
    }
    
    /**
     * increases health by a specified number 0 to max_health default max_health
     * is 100.
     */
    public void getRepairs( int bricks )
    {
        if ( health + bricks <= 100 ) health += bricks;
    }
    
    // SelectableInstance Interface methods
    /**
     * @return the command queue in array format.
     */
    public SelectableCommand[] getCommands()
    {
        if ( isCompleted() )
        {
            SelectableCommand[] zeCommands = new SelectableCommand[ availCommands.size() ];
            zeCommands = availCommands.toArray( zeCommands );
            return zeCommands;
        }
        else
        {
            return new SelectableCommand[ 0 ];
        }
    }
    
    /**
     * adds a command to the command queue of the structure.
     */
    public void addCommandToQueue( SelectableCommand command )
    {
        commands.add( command.getCommand() );
    }
    
    /**
     * returns a string representation of the structure.
     */
    public String getName()
    {
        return name;
    }
    
    /**
     * Returns an object representing the type of structure and its health.
     * 
     * @return an object containing the type of the structure and its health.
     */
    public InstanceTypeAndHealth[] getStatus()
    {
        InstanceTypeAndHealth[] itahc = new InstanceTypeAndHealthClass[ 1 ];
        itahc[ 0 ] = new InstanceTypeAndHealthClass( type, health );
        
        return itahc;
    }
    
    /**
     * clears the command queue.
     */
    public void cancelPendingOrders()
    {
        commands.clear();
        commands.add( new Wait() );
    }
    
    // SelectableStructure Interface methods
    /**
     * returns an array representation of the command queue.
     */
    public SelectableCommand[] getQueue()
    {
        SelectableCommand[] sCommandArray = new SelectableCommand[ commands.size() ];
        sCommandArray = commands.toArray( sCommandArray );
        
        return sCommandArray;
    }
    
    // Individuals Interface methods
    /**
     * returns health. 0 to max_health default max_health is 100.
     * 
     * Deviation: Not needed. Handled by getStatus
     * 
     * @deprecated
     */
    public int getHealth()
    {
        throw new UnsupportedOperationException();
    }
    
    /**
     * default max_health = 100
     * 
     * Deviation: Not needed. Handled by getStatus
     * 
     * @deprecated
     */
    public int getMaxHealth()
    {
        throw new UnsupportedOperationException();
    }
    
    /**
     * Deviation: not needed. Handled within takeDamage.
     * 
     * @deprecated
     * @return armor. value 0 to 100. Used to scale damage taken.
     */
    public int getArmor()
    {
        throw new UnsupportedOperationException();
    }
    
    /**
     * @return attack power 0 to 100.
     */
    public int getAttackPower()
    {
        return attack_power;
    }
    
    /**
     * Deviation: not needed. Handled within takeDamage.
     * 
     * @deprecated
     * @return defense power 0 to 100.
     */
    public int getDefensePower()
    {
        throw new UnsupportedOperationException();
    }
    
    /**
     * @return the type of structure (base, tower, etc)
     */
    public InstanceType getInstanceType()
    {
        return type;
    }
    
    // ModelInstance interface methods
    /**
     * removes the command in the front of the queue.
     */
    public void popCommand()
    {
        commands.pop();
        
        if ( commands.isEmpty() ) commands.add( new Wait() );
    }
    
    /**
     * clears the command queue.
     * 
     * Deviation: Handled by cancelPendingOrders
     * 
     * @deprecated
     * @deprecated does same thing as cancelPendingOrders.
     */
    public void clearQueue()
    {
        throw new UnsupportedOperationException();
    }
    
    /**
     * must be called 10 times in order to power up. This is because it must
     * take 10 ticks to power up.
     */
    public void powerUp()
    {
        state.incrementPower();
    }
    
    /**
     * immediately sets state to powered down.
     */
    public void powerDown()
    {
        state.powerDown();
    }
    
    /**
     * @return if it is powered on or not.
     */
    public boolean isPowered()
    {
        return state.isPowered();
    }
    
    // destroy structure
    /**
     * destroys the structure. sets all appropriate attributes to an ineffective
     * value. does not delete the object.
     */
    public void decommission()
    {
        zeIntRecycler.recycleInt( id );
        health = 0;
    }
    
    /**
     * @return a map representing the resource type and amount deducted from
     *         player on each tick because this structure exists. based on the
     *         state of the Structure. (powered up, down, etc.)
     */
    public Map< ResourceType, Integer > getUpkeep()
    {
        // A unit/structure/army may be powered down�
        // in that state it consumes 25% of its normal resources
        if ( !state.isPowered() )
        {
            if ( upkeep.containsKey( ResourceType.RED ) )
                upkeep.put( ResourceType.RED, (int) (upkeep.get( ResourceType.RED ) * 0.25) );
            if ( upkeep.containsKey( ResourceType.BLUE ) )
                upkeep.put( ResourceType.BLUE, (int) (upkeep.get( ResourceType.BLUE ) * 0.25) );
            if ( upkeep.containsKey( ResourceType.GREEN ) )
                upkeep.put( ResourceType.GREEN, (int) (upkeep.get( ResourceType.GREEN ) * 0.25) );
            
        }
        // otherwise uses 100% resources.
        return upkeep;
    }
    
    /**
     * @author - ryan
     */
    public void heal( int amount )
    {
        this.health = (health + amount) % max_health;
    }
    
    /**
     * @return the front command on the queue. Does not remove the command from
     *         the queue.
     */
    public Command onTick( Player player )
    {
        return commands.getFirst();
    }
    
    // Designer-added methods.
    public int getID()
    {
        return id;
    }
    
}
